using DefaultDspPluginsLib;
using DefaultDspPluginsLib.Depricated;
using SoundEngine3;
using SoundEngine3.AudioProcessing;
using SoundEngine3.AudioProcessing.Generators;
using SoundEngine3.DspPlugins;
using SoundEngine3.DspPlugins.Meta;
using SoundEngine3.MIDI.Meta;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ZquenceStudio3.Core;
using ZquenceStudio3.Core.DspPlugins;
using ZquenceStudio3.DspPlugins;

namespace ZquenceStudio.DefaultDspPlugins.Effects
{
    public class SidechainingDescription : IDspPlugin
    {
        public DspPluginInfo GetDspPluginInfo()
        {
            return new DspPluginInfo("Side chaining", DspType.Effect);
        }

        public BaseDspPluginInstance CreateDsp(ZquenceStudio3Context context, DspPluginInfo info)
        {
            return new Sidechaining(context, info);
        }
    }

    public interface ISidechainingUI
    {
        void Update();
        void UpdateSamplesPerBar(double value);
        void RequestRender();
    }

    public class Sidechaining : CoreDspEffectPlugin
    {
#if !Portable

        [DllImport(NativeSoundEngineDllImport.Dll)]
        public static extern IntPtr Effect_SideChaining_Create();

        [DllImport(NativeSoundEngineDllImport.Dll)]
        public static extern void Effect_SideChaining_Delete(IntPtr ptr);

        [DllImport(NativeSoundEngineDllImport.Dll)]
        public static extern void Effect_SideChaining_Setup(IntPtr ptr, double volume, double fallback, double curve, double distance, double samplesPerBeat);
        
        [DllImport(NativeSoundEngineDllImport.Dll)]
        public static extern void Effect_SideChaining_Process(IntPtr ptr, double sideChainPosition, int length
            , double[] wav_in_left, double[] wav_in_right, double[] wav_out_left, double[] wav_out_right);
#endif

        private ISidechainingUI mUI;
        private IntPtr mSideChainingPtr;
        private Effect_SideChaining mSideChanining;

        private double mVolyme;
        private double mFallback;
        private double mFallbackCurve;
        private double mFallbackDistance;

        public ParameterPropertiesDirect volyme;
        public ParameterPropertiesDirect fallback;
        public ParameterPropertiesDirect fallbackCurve;
        public ParameterPropertiesDirect fallbackDistance;
        
        public EnvelopeManager2 EnvelopeManager { get { return mSideChanining.EnvelopeManager; } }

        public double Volume { get { return mVolyme; } }

        public Sidechaining(ZquenceStudio3Context application, DspPluginInfo info)
            : base(application, info)
        {
#if !Portable
            mSideChainingPtr = Effect_SideChaining_Create();
#endif
            mSideChanining = new DefaultDspPluginsLib.Depricated.Effect_SideChaining();

            CreateParameters();

            SupportAudioProcessing = true;
        }

        ~Sidechaining()
        {
#if !Portable
            Effect_SideChaining_Delete(mSideChainingPtr);
#endif
        }

        public void SetUI(ISidechainingUI ui)
        {
            mUI = ui;
        }
        
        private void CreateParameters()
        {
            // Volyme.
            volyme = new ParameterPropertiesDirect(this);
            volyme.Label = "Volyme";
            volyme.DirectGet += delegate() { return mVolyme; };
            volyme.DirectSet += delegate(double value)
            {
                value = Math.Min(value, 1);
                value = Math.Max(value, 0);
                mVolyme = value;
                Update();
            };
            volyme.MinValue = 0;
            volyme.MaxValue = 1;
            AddParameter(volyme);

            // Fallback.
            fallback = new ParameterPropertiesDirect(this);
            fallback.Label = "Fallback";
            fallback.DirectGet += delegate () { return mFallback; };
            fallback.DirectSet += delegate (double value)
            {
                value = Math.Min(value, 1);
                value = Math.Max(value, 0);
                mFallback = value;
                Update();
            };
            fallback.MinValue = 0;
            fallback.MaxValue = 1;
            AddParameter(fallback);

            // Fallback curve.
            fallbackCurve = new ParameterPropertiesDirect(this);
            fallbackCurve.Label = "Fallback curve";
            fallbackCurve.DirectGet += delegate () { return mFallbackCurve; };
            fallbackCurve.DirectSet += delegate (double value)
            {
                value = Math.Min(value, 1);
                value = Math.Max(value, -1);
                mFallbackCurve = value;
                Update();
            };
            fallbackCurve.MinValue = -1;
            fallbackCurve.MaxValue = 1;
            AddParameter(fallbackCurve);

            // Fallback distance.
            fallbackDistance = new ParameterPropertiesDirect(this);
            fallbackDistance.Label = "Fallback distance";
            fallbackDistance.DirectGet += delegate () { return mFallbackDistance; };
            fallbackDistance.DirectSet += delegate (double value)
            {
                value = Math.Min(value, 1);
                value = Math.Max(value, 0);
                mFallbackDistance = value;
                Update();
            };
            fallbackDistance.MinValue = 0;
            fallbackDistance.MaxValue = 1;
            AddParameter(fallbackDistance);

            volyme.RawValue = 1;
            fallback.RawValue = 0.9;
            fallbackCurve.RawValue = 0.2;
            fallbackDistance.RawValue = 0;
        }

        void Update()
        {
            double fallback1 = 1 - mFallback;
            int beatSamples = (int)SoundEngineContext.PPQ_To_Samples(SoundEngineContext.PpqResolution);
            mSideChanining.Setup(mVolyme, fallback1, mFallbackCurve, mFallbackDistance, beatSamples);

#if !Portable
            Effect_SideChaining_Setup(mSideChainingPtr, mVolyme, fallback1, mFallbackCurve, mFallbackDistance, beatSamples);
#endif

            mUI?.Update();
            mUI?.RequestRender();
        }
        
        public override void Process(BaseAudioBuffer input, BaseAudioBuffer output)
        {
            double[] sound_left = input.Channels[0].Samples;
            double[] sound_right = input.Channels[1].Samples;
            double[] sound_left_out = output.Channels[0].Samples;
            double[] sound_right_out = output.Channels[1].Samples;

            if (SoundEngineContext.IsPlaying)
            {
                double bufferSizeInPqq = SoundEngineContext.Samples_To_PPQ(SoundEngineContext.BufferSize);
                double ppqPos = (SoundEngineContext.PpqPosition - bufferSizeInPqq) % SoundEngineContext.PpqResolution + SoundEngineContext.PpqResolution;
                double samplePos = SoundEngineContext.PPQ_To_Samples(ppqPos);

                if (NativeSoundEngineDllImport.UseBenchmarking)
                {
                    ZquenceStudio3.Core.Utils.ProfilerMeter.Start1("SideChaning");
#if !Portable
                    Effect_SideChaining_Process(mSideChainingPtr, samplePos, sound_left.Length ,sound_left, sound_right, sound_left_out, sound_right_out);
#endif
                    ZquenceStudio3.Core.Utils.ProfilerMeter.Stop1("SideChaning");
                    ZquenceStudio3.Core.Utils.ProfilerMeter.Start2("SideChaning");
                    mSideChanining.Process(samplePos, sound_left, sound_right, sound_left_out, sound_right_out);
                    ZquenceStudio3.Core.Utils.ProfilerMeter.Stop2("SideChaning");
                }
                else
                {
                    if (NativeSoundEngineDllImport.UseNativeProcessing)
                    {
#if !Portable
                        Effect_SideChaining_Process(mSideChainingPtr, samplePos, sound_left.Length, sound_left, sound_right, sound_left_out, sound_right_out);
#endif
                    }
                    else
                    {
                        mSideChanining.Process(samplePos, sound_left, sound_right, sound_left_out, sound_right_out);
                    }
                }
            }
            else
            {
                Array.Copy(sound_left, sound_left_out, sound_left.Length);
                Array.Copy(sound_right, sound_right_out, sound_left.Length);
            }
        }

        /// <summary>
        /// Process the side chaining.
        /// </summary>
        /// <param name="input">Input sound.</param>
        /// <param name="output">Ouput sound.</param>
        /// <param name="soundEngine">SoundEngine that contains sequencer and time.</param>
        /// <param name="volyme">Sidechaning volyme.</param>
        /// <param name="chainingPosition">Sidechaining position.</param>
        private void ProcessSidechaining(BaseAudioBuffer input, BaseAudioBuffer output, SoundEngine soundEngine, ref double chainingPosition)
        {
        }
        
        public override Version Version
        {
            get { return DspPluginInfo.DefaultVersion; }
        }

        public override string PluginInfo
        {
            get { return DspPluginInfo.DefaultPluginInfo; }
        }

        public override void Dispose()
        {

        }

        protected override void OnProcessEvents(List<MidiMessage> midiMessages)
        {

        }

        protected override void OnChangeBufferSize(int bufferSize)
        {
            Update();
        }

        protected override void OnChangeSampleRate(double sampleRate)
        {
            Update();
        }

        protected override void OnChangeInputChannels(int channels)
        {

        }

        protected override void OnChangeOutputChannels(int channels)
        {

        }
    }
}
